package in

import (
	"fmt"
	"log"
	"math/rand"
	"sort"
	"sync"

	// "gitee.com/dark.H/DataDash/in"
	"github.com/sjwhitworth/golearn/pca"
	"gonum.org/v1/gonum/mat"
)

type row []float64

type NodeView map[int]G

const (
	Laplacian  = 1
	FLOAT_ZERO = 0.001
)

// type column []float64

// This example shows how simple user types can be constructed to
// implement basic vector functionality within the mat package.
type NodeIDS []int
type Edges map[int]int

func (nodes NodeIDS) GetNode(qid int) int {
	for no, id := range nodes {
		if id == qid {
			return no
		}
	}
	return -1
}

func (nodes NodeIDS) NetworkWithPCA(edges Edges, lineStrong float64) (points Datas) {
	weight := len(edges) + 1
	aj := GetAdjacencyMat(nodes, edges, float64(weight))
	aj.Apply(func(i, j int, v float64) float64 {
		return rand.Float64()
	}, aj)

	pointsPca := PCAPoints(aj)
	for no, nodeID := range nodes {
		po := G{
			"id": nodeID,
			"x":  pointsPca.At(no, 0) * lineStrong,
			"y":  pointsPca.At(no, 1) * lineStrong,
		}
		points = append(points, po)
	}
	return
}

func GetAdjacencyMat(nodes NodeIDS, edges Edges, weight float64) *mat.Dense {
	adjacency := mat.NewDense(len(nodes), len(nodes), nil)
	for fromId := range edges {
		toId := edges[fromId]
		i := nodes.GetNode(fromId)
		j := nodes.GetNode(toId)
		if i == -1 || j == -1 {
			continue
		}
		if i == j {
			continue
		}
		adjacency.Set(i, j, weight)
		adjacency.Set(j, i, weight)
	}
	return adjacency
}

func GetDeg(adjacency *mat.Dense) *mat.Dense {
	i, j := adjacency.Dims()
	copyAdja := mat.NewDense(i, j, nil)
	ci := 0
	for {
		if ci >= j {
			break
		}
		vec := adjacency.ColView(ci)

		ji := 0
		deg := 0
		for {
			if ji >= i {
				break
			}
			if vec.At(ji, 0) != float64(0) {

				deg++
			}
			ji++
		}
		// fmt.Println(deg)
		copyAdja.Set(ci, ci, float64(deg))
		ci++
	}
	return copyAdja
}

func GetLaplacianMat(adjacency *mat.Dense) *mat.Dense {
	i, j := adjacency.Dims()
	copyAdja := mat.NewDense(i, j, nil)
	copyAdja.Copy(adjacency)

	dmat := GetDeg(copyAdja)
	// out := mat.NewDense(i, j, nil)
	dmat.Sub(dmat, adjacency)
	return dmat
}
func ShowVec(vec mat.Vector) {
	for i := 0; i < vec.Len(); i++ {
		fmt.Printf("%f ,", vec.At(i, 0))
	}
	fmt.Println()
}
func CountNoZero(vec mat.Vector) (o int) {
	for i := 0; i < vec.Len(); i++ {
		v := vec.At(i, 0)
		if !IsZero(v) {
			o++
		}
	}
	// fmt.Println()
	return
}

func (nodes NodeIDS) GetSubLaplacianGraph(rawAj *mat.Dense, selectVec mat.Vector, bias, scale float32) (subPoints Datas) {
	/*
		The code in this area,  thanks to  Dr. Mao for  Very much  providing a marvelous way to calculate the point-location.
	*/
	var rawMap []int
	// var values []float64
	for i := 0; i < selectVec.Len(); i++ {
		v := selectVec.At(i, 0)
		if !IsZero(v) {
			rawMap = append(rawMap, i)
		}
	}
	dimLen := len(rawMap)

	ColorL("sub graph nodes:", dimLen)
	subAj := mat.NewDense(dimLen, dimLen, nil)
	for subi, rawi := range rawMap {
		for subj, rawj := range rawMap {
			if subi == subj {
				continue
			}
			subAj.Set(subi, subj, rawAj.At(rawi, rawj))
		}
	}
	lap := GetLaplacianMat(subAj)
	// fmt.Println(rawMap)
	// fmt.Println(mat.Formatted(lap))
	var svdFactory mat.SVD
	if ok := svdFactory.Factorize(lap, mat.SVDFull); ok {
		V := mat.NewDense(dimLen, dimLen, nil)
		svdFactory.VTo(V)
		// var XVec,YVec mat.Vector
		// for no, v := range svdFactory.Values(){
		XVec := V.ColView(dimLen - 3)
		YVec := V.ColView(dimLen - 2)
		// }

		for no, selectId := range rawMap {
			x := float32(XVec.At(no, 0))
			y := float32(YVec.At(no, 0))
			// node := nodes[selectId]
			nodeid := nodes[selectId]

			// ColorL("Point:", nodeid, "Loc:", x*scale+(bias*100), y*scale)
			po := G{
				"id": nodeid,
				"x":  x*scale + (bias * 100),
				"y":  y*scale + (bias * 100),
			}
			subPoints = append(subPoints, po)
		}
	}

	return

}

func IsZero(x float64) bool {
	return fmt.Sprintf("%f", x) == "0.000000"
}

func (nodes NodeIDS) NetworkWithLaplacian(edges Edges, lineStrong float64) (points Datas) {
	weight := 1
	aj := GetAdjacencyMat(nodes, edges, float64(weight))

	// aj.Apply(func(i, j int, v float64) float64 {
	// 	if int(v) < 0 {
	// 		return (rand.Float64() - 1)
	// 	}
	// 	return v
	// }, aj)
	la := GetLaplacianMat(aj)

	fmt.Println(mat.Formatted(la))
	var svdE mat.SVD
	ok := svdE.Factorize(la, mat.SVDFull)

	if !ok {
		log.Println("svdEen factorize failed!")
	}

	_, j := aj.Dims()
	ev := mat.NewDense(j, j, nil)
	var values []float64
	svdE.VTo(ev)
	// fmt.Println(mat.Formatted(ev))
	// log.Fatal("end")
	values = svdE.Values(values)
	// matKeys := make(map[float64]int)
	// for no, v := range values {
	// 	matKeys[v] = no
	// }
	ColorL("Svd Value:", values)
	ColorL(mat.Formatted(aj))

	ColorL(mat.Formatted(la))

	// Append all subgraph to points
	channelNodes := make(chan Datas, 5)
	var waitGroup sync.WaitGroup
	go func() {
		// waitGroup.Add(1)
		for {
			subpoints := <-channelNodes
			if len(subpoints) > 1 {

				points = append(points, subpoints...)
			}
		}
		// waitGroup.Done()
		// ColorL("Sub graph count:", len(points))
	}()
	C := 0
	for n, v := range values {
		// if AbsLess(v, FLOAT_ZERO) {
		if fmt.Sprintf("%f", v) == "0.000000" {
			testVec := ev.ColView(n)
			if count := CountNoZero(testVec); count < 3 {
				continue
			} else {
				ColorL("subgraph node num:", count, testVec.Len())
			}
			// calculate sub graph in async way.
			C++
			ColorL("Graph  Eigen:", v, C)
			ShowVec(testVec)
			waitGroup.Add(1)
			go func() {
				subPoints := nodes.GetSubLaplacianGraph(aj, testVec, float32(C), float32(10))
				channelNodes <- subPoints
				defer waitGroup.Done()
			}()

		} else {
			fmt.Printf("%f\n", v)
		}
	}
	channelNodes <- Datas{G{"TAG": "END"}}
	// sort.Slice(values, func(i, j int) bool {
	// 	return values[i] < values[j]
	// })

	// fmt.Println(values)
	// var xlines mat.Vector
	// var ylines mat.Vector
	// usedX := false
	// usedY := false
	// usedfloat := float32(0)
	// jumpFirst := 0
	// for _, v := range values {
	// 	if usedX && usedY {
	// 		break
	// 	}
	// 	if v > float64(0.0001) {
	// 		if jumpFirst < 0 {
	// 			jumpFirst++
	// 			continue
	// 		}
	// 		if !usedX {
	// 			xlines = ev.RowView(matKeys[v])
	// 			ColorL("used", v, matKeys[v])
	// 			usedX = true
	// 			usedfloat = float32(v)
	// 			continue
	// 		} else {
	// 			if float32(v) == usedfloat {
	// 				continue
	// 			}
	// 			ColorL("usedy", v, matKeys[v])
	// 			ylines = ev.RowView(matKeys[v])
	// 			usedY = true
	// 		}
	// 	}
	// }

	// for no, nodeID := range nodes {
	// 	x := xlines.At(no, 0)
	// 	y := ylines.At(no, 0)
	// 	if x == 0 && y == 0 {
	// 		x = (rand.Float64() - 0.5) * lineStrong
	// 		y = (rand.Float64() - 0.5) * lineStrong
	// 	}
	// 	po := G{
	// 		"id": nodeID,
	// 		"x":  x * lineStrong,
	// 		"y":  y * lineStrong,
	// 	}
	// 	points = append(points, po)
	// }
	waitGroup.Wait()
	return

}

func PCAPoints(adjacency *mat.Dense) (pcaMat *mat.Dense) {

	pcaTran := pca.NewPCA(2)
	pcaMat = pcaTran.FitTransform(adjacency)
	return
}

func (nodeview NodeView) CaculatePoints(edges Edges, lineStrongPoint int, GraphyAlgrm int) NodeView {
	var nodes NodeIDS
	edgesCopy := make(Edges)
	for id := range edges {
		if _, ok := nodeview[id]; ok {
			edgesCopy[id] = edges[id]
		}
	}
	for id := range nodeview {
		nodes = append(nodes, id)
	}
	newnodeView := make(NodeView)
	var points Datas
	switch GraphyAlgrm {
	case Laplacian:
		points = nodes.NetworkWithLaplacian(edgesCopy, float64(lineStrongPoint))

	default:
		points = nodes.NetworkWithPCA(edgesCopy, float64(lineStrongPoint))

	}
	for _, point := range points {
		qid := point["id"].(int)
		// ColorL("Qid:", qid)
		newnodeView[qid] = point
	}
	return newnodeView
}

// func TestPCAPoints() {
// 	// Perform the cross product of [1 2 3 4] and [1 2 3].
// 	r := mat.NewDense(1, 4, row{1, 2, 3, 4})
// 	c := mat.NewDense(3, 1, row{1, 2, 3})

// 	var m mat.Dense
// 	m.Mul(c, r)

// 	fmt.Println(mat.Formatted(&m))

// 	edges := make(Edges)
// 	nodes := NodeIDS{1, 2, 3, 4, 5}
// 	edges[1] = 3
// 	edges[1] = 2
// 	edges[4] = 5
// 	aj := GetAdjacencyMat(nodes, edges, 1)

// 	// fmt.Println(mat.Formatted(aj))
// 	pc := PCAPoints(aj)
// 	// fmt.Println(mat.Formatted(pc))
// 	// for _, d :=

// }
func (edges Edges) ToDatas(nodes NodeView, edgesLabels map[int]map[int]string) (outEdges Datas) {
	allCount := len(edges)
	for fromID := range edges {
		toID := edges[fromID]

		label := edgesLabels[fromID][toID]
		if data, ok := nodes[fromID]; ok {
			data["x"] = rand.Float32() * float32(allCount)
			data["y"] = rand.Float32() * float32(allCount)
			if toData, ok2 := nodes[toID]; ok2 {
				toData["x"] = rand.Float32() * float32(allCount)
				toData["y"] = rand.Float32() * float32(allCount)
				outEdges = append(outEdges, G{
					"from":  fromID,
					"to":    toID,
					"fromP": data,
					"toP":   toData,
					"label": label,
				})
				ColorL("nodes exists:", "from", fromID, "to", toID)
			}

		} else {
			outEdges = append(outEdges, G{
				"from":  fromID,
				"to":    toID,
				"label": label,
				"fromP": G{
					"id": fromID,
					"x":  rand.Float32() * float32(allCount),
					"y":  rand.Float32() * float32(allCount),
				},
				"toP": G{
					"id": toID,
					"x":  rand.Float32() * float32(allCount),
					"y":  rand.Float32() * float32(allCount),
				},
			})
			ColorL("from", fromID, "to", toID)
		}
	}
	return
}

type MetaInfo struct {
	FromID int
	ToID   int
	X      float32
	Y      float32
	Label  string
	Title  string
}

type TreeNode struct {
	Data         G
	ID           int
	SubNodeCount int
	From         *TreeNode
	To           map[int]*TreeNode
	Meta         *MetaInfo
}
type Tree struct {
	Top   []*TreeNode
	Tails []*TreeNode
	Nodes map[int]*TreeNode
}

func (g G) ToTreeNode() (tnode *TreeNode) {
	if id, ok := g["id"].(int); ok {
		if data, ok := g["data"]; ok {
			tnode = new(TreeNode)
			tnode.ID = id
			tnode.To = make(map[int]*TreeNode)
			tnode.Data = data.(G)
		}
		if frID, ok := g["from"]; ok {
			tnode.Meta = new(MetaInfo)
			tnode.Meta.FromID = frID.(int)
			tnode.Meta.ToID = g["to"].(int)

		}
		if x, ok := g["x"]; ok {
			if tnode.Meta == nil {
				tnode.Meta = new(MetaInfo)
			}
			tnode.Meta.X = x.(float32)
			tnode.Meta.Y = g["y"].(float32)
		}
		if label, ok := g["label"]; ok && tnode.Meta != nil {
			if tnode.Meta == nil {
				tnode.Meta = new(MetaInfo)
			}
			tnode.Meta.Label = label.(string)
		}
		if title, ok := g["Title"]; ok && tnode.Meta != nil {
			if tnode.Meta == nil {
				tnode.Meta = new(MetaInfo)
			}
			tnode.Meta.Title = title.(string)
		}
	} else {
		log.Println("[Key Error]:", "lack key noid or data!")
	}
	return
}

func (g G) FromTreeNode(fr *TreeNode) *TreeNode {
	if id, ok := g["id"].(int); ok {
		if data, ok := g["data"]; ok {
			treeNode := new(TreeNode)
			treeNode.To = make(map[int]*TreeNode)
			treeNode.Data = data.(G)
			treeNode.From = fr
			treeNode.ID = id
			fr.SubNodeCount++
			fr.To[id] = treeNode
			return treeNode
		}
	}
	return nil
}

func (tnode *TreeNode) FromNode(ftnode *TreeNode) {
	tnode.From = ftnode

	ftnode.To[tnode.ID] = tnode
	ftnode.SubNodeCount += tnode.SubNodeCount + 1
	sn := ftnode.From
	count := 0
	for {
		count++
		if sn == nil || count > 128 {
			break
		}
		sn.SubNodeCount += tnode.SubNodeCount
		sn = sn.From
	}
}

func (tnode *TreeNode) ToData() (g G) {
	g = G{
		"id":   tnode.ID,
		"data": tnode.Data,
	}
	if tnode.Meta != nil {
		g["x"] = tnode.Meta.X
		g["y"] = tnode.Meta.Y
		g["label"] = tnode.Meta.Label
		g["title"] = tnode.Meta.Title
	}
	return
}

// func (tnode *TreeNode) RandomPoint()

func (datas Datas) ConnectedWith(ifConnected func(one G, two G, isSame, connected bool) bool) (EdegeDatas Datas) {
	connected := make(map[int]map[int]bool)
	allCount := len(datas)
	bar := NewBar(int64(allCount * allCount))
	Per := int64(allCount * allCount / 100)
	if Per == 0 {
		Per = 1
	}
	// subNodeNum := make(map[int]int)
	for ID, fg := range datas {
		if connected[ID] == nil {
			connected[ID] = make(map[int]bool)
		}
		// connectNum := 0
		for TID, tg := range datas {
			if connected[TID] == nil {
				connected[TID] = make(map[int]bool)
			}
			bar.Increment(1)
			if bar.NowSize%Per == 0 {
				bar.Log("Dealing Connected Data: ", bar.NowSize)
			}
			if ifConnected(fg, tg, ID == TID, connected[ID][TID] || connected[TID][ID]) {
				connected[ID][TID] = true
				node := G{

					"from":  fg["id"],
					"fromP": fg,
					"to":    tg["id"],
					"toP":   tg,
				}
				// connectNum++
				// ColorD(fg)
				// log.Fatal()
				EdegeDatas = append(EdegeDatas, node)
			}
		}
		// subNodeNum[fg["id"].(int)] = connectNum

		// ColorL(ID, subNodeNum[ID])
	}
	// for _, edge := range EdegeDatas {
	// 	edge["width"] = subNodeNum[edge["to"].(int)]
	// 	// ColorL(edge["to"].(int), subNodeNum[edge["to"].(int)])
	// }
	return
}

func (datas Datas) TryToGetTree() (tree *Tree, err error) {
	tree = new(Tree)
	tree.Nodes = make(map[int]*TreeNode)
	// tmpView := make(NodeView)
	// waitID := []int{}
	for _, d := range datas {
		fid := d["from"].(int)
		tid := d["to"].(int)
		if fid == tid {
			log.Fatal("Same Error:", fid)
		}
		var ftnode, ttnode *TreeNode
		// var ok bool
		if ftnode, _ = tree.Nodes[fid]; ftnode == nil {
			ftnode = d["fromP"].(G).ToTreeNode()
		}
		if ttnode, _ = tree.Nodes[tid]; ttnode == nil {
			ttnode = d["toP"].(G).ToTreeNode()
		}

		ttnode.FromNode(ftnode)
		tree.Nodes[fid] = ftnode
		tree.Nodes[tid] = ttnode

	}

	for _, tnode := range tree.Nodes {
		if tnode.From == nil {
			tree.Top = append(tree.Top, tnode)
			// log.Println(tnode)
		}
		if len(tnode.To) == 0 {
			tree.Tails = append(tree.Tails, tnode)
		}
	}
	sort.Slice(tree.Top, func(i, j int) bool {
		return tree.Top[i].SubNodeCount > tree.Top[j].SubNodeCount
	})
	return
}

func (tree *Tree) Levels(limit int) (out Datas) {
	// all := 0
	// bar := NewBar()
	thisLevel := make(map[int]*TreeNode)
	for _, node := range tree.Top {
		thisLevel[node.ID] = node
	}
	num := 100
	for l := 0; l < num; l++ {
		other := make(map[int]*TreeNode)
		for fid, n := range thisLevel {
			for id, nt := range n.To {
				other[id] = nt
				if len(out) < limit {
					out = append(out, G{
						"from":  fid,
						"to":    id,
						"fromP": n.ToData(),
						"toP":   nt.ToData(),
						"width": nt.SubNodeCount,
					})
				} else {
					return out
				}
			}

		}
		thisLevel = other
	}

	return
}
